<?php

/*
Copyright 2009-2011 Sam Weiss
All Rights Reserved.

This file is part of Spark/Plug.

Spark/Plug is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

if (!defined('spark/plug'))
{
	header('HTTP/1.1 403 Forbidden');
	exit('<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"><html><head><title>403 Forbidden</title></head><body><h1>Forbidden</h1><p>You don\'t have permission to access the requested resource on this server.</p></body></html>');
}

// -----------------------------------------------------------------------------

abstract class _SparkSessionStore extends SparkPlug
{
	protected $_name;
	protected $_updateTime;
	protected $_lifetime;
	protected $_path;
	protected $_domain;
	protected $_matchIP;
	protected $_matchUserAgent;
	protected $_encrypted;
	protected $_encryptionKey;
	private   $_curTime;
	
	private static $_init;

	// --------------------------------------------------------------------------

	public function __construct($params)
	{
		parent::__construct($params);

		$this->_name = $params['name'];
		$this->_updateTime = $params['update_time'];
		$this->_lifetime = $params['lifetime'];
		$this->_path = $params['path'];
		$this->_domain = $params['domain'];
		$this->_matchIP = $params['match_ip'];
		$this->_matchUserAgent = $params['match_user_agent'];
		$this->_encrypted = $params['encrypted'];
		$this->_encryptionKey = $params['encryption_key'];

		$this->_curTime = time();
		
		if (!empty(self::$_init['spark_session_init_' . $this->_name]))
		{
			return;
		}
		
		$this->start();
		
		$userTrack = $this->get('user_track');

		if (!$this->_validate($userTrack))
		{
			$this->clear();
			$this->_create();
		}
		else
		{
			$this->_update($userTrack);
		}

		$this->_flashLoad();
		self::$_init['spark_session_init_' . $this->_name] = true;
	}
	
	// --------------------------------------------------------------------------

	public function flashGet($key)
	{
		$flash = $this->get('_flash_');
		return @$flash['old'][$key];
	}

	// --------------------------------------------------------------------------

	public function flashSet($key, $val)
	{
		$flash = $this->get('_flash_');
		$flash['new'][$key] = $val;
		$this->set('_flash_', $flash);
	}

	// --------------------------------------------------------------------------

	public function flashRemove($key)
	{
		$flash = $this->get('_flash_');
		unset($flash['new'][$key]);
		unset($flash['old'][$key]);
		$this->set('_flash_', $flash);
	}

	// --------------------------------------------------------------------------

	public function flashKeep($key)
	{
		$flash = $this->get('_flash_');
		if (isset($flash['old'][$key]))
		{
			$flash['new'][$key] =& $flash['old'][$key];
			$this->set('_flash_', $flash);
			return $flash['old'][$key];
		}
		return NULL;
	}

	// --------------------------------------------------------------------------

	public function flashKeepAll()
	{
		$flash = $this->get('_flash_');
		if (!empty($flash['old']))
		{
			foreach (array_keys($flash['old']) as $key)
			{
				$flash['new'][$key] =& $flash['old'][$key];
			}
			$this->set('_flash_', $flash);
		}
	}

	// --------------------------------------------------------------------------

	// Protected Methods

	// --------------------------------------------------------------------------

	protected function destruct()
	{
		unset(self::$_init['spark_session_init_' . $this->_name]);
	}
	
	// --------------------------------------------------------------------------

	// Private Methods

	// --------------------------------------------------------------------------

	private function _validate($userTrack)
	{
		if (!$userTrack || !isset($userTrack['session_id']) || !isset($userTrack['session_ip']) || !isset($userTrack['user_agent']) || !isset($userTrack['last_access']))
		{
			return false;
		}

		if ($this->_matchIP && !$this->_validate_ip($userTrack['session_ip']))
		{
			return false;
		}

		if ($this->_matchUserAgent && !$this->_validate_user_agent($userTrack['user_agent']))
		{
			return false;
		}

		if ($this->_lifetime && !$this->_validate_lifetime($userTrack['last_access']))
		{
			return false;
		}

		return true;
	}
	
	// --------------------------------------------------------------------------

	private function _validate_ip($savedIP)
	{
		$curIP = SparkUtil::remote_ip();

		if (!SparkUtil::match_ip($curIP, $savedIP, $this->_matchIP))
		{
			// notify interested parties about the mismatch
		
			$this->observer->notify('SparkSessionStore:validate_ip:fail',  "client IP mismatch, {$savedIP} -> {$curIP}");
			return false;
		}

		return true;
	}
	
	// --------------------------------------------------------------------------

	private function _validate_user_agent($savedUserAgent)
	{
		return $savedUserAgent === trim(substr(SparkUtil::user_agent(), 0, 50));
	}
	
	// --------------------------------------------------------------------------

	private function _validate_lifetime($lastAccess)
	{
		return ($this->_curTime - $lastAccess) <= $this->_lifetime;
	}
	
	// --------------------------------------------------------------------------

	private function _create()
	{
		$userTrack['session_ip'] = SparkUtil::remote_ip();
		$userTrack['user_agent'] = trim(substr(SparkUtil::user_agent(), 0, 50));
		$userTrack['session_id'] = md5(uniqid(mt_rand() . $userTrack['session_ip'], true));
		$userTrack['last_access'] = $this->_curTime;

		$this->set('user_track', $userTrack);
	}
	
	// --------------------------------------------------------------------------

	private function _update(&$userTrack)
	{
		// time to update the session yet?

		if (($userTrack['last_access'] + $this->_updateTime) < $this->_curTime)
		{
			$userTrack['session_id'] = md5(uniqid(mt_rand() . $userTrack['session_ip'], true));
			$userTrack['last_access'] = $this->_curTime;
			$this->set('user_track', $userTrack);
		}
	}
	
	// --------------------------------------------------------------------------

	private function _flashLoad()
	{
		$flash = $this->get('_flash_');

		// out with the old...
		
		$flash['old'] = array();
		
		// in with the new...

		if (isset($flash['new']))
		{
			foreach (array_keys($flash['new']) as $key)
			{
				$flash['old'][$key] =& $flash['new'][$key];
			}
			unset($flash['new']);
		}
		
		$this->set('_flash_', $flash);
	}

	// The following functions **must** be overridden by adapter classes for database-specific functions.

	// --------------------------------------------------------------------------

	abstract public function start();

	// --------------------------------------------------------------------------

	abstract public function get($key, $default = NULL);

	// --------------------------------------------------------------------------

	abstract public function set($key, $val);

	// --------------------------------------------------------------------------

	abstract public function remove($key);

	// --------------------------------------------------------------------------

	abstract public function clear();

	// --------------------------------------------------------------------------

	abstract public function destroy();

	// --------------------------------------------------------------------------
}
